home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / libs / otm3d095 / otmpoly.doc < prev    next >
Text File  |  1994-12-02  |  30KB  |  711 lines

  1.  
  2.         OTMPOLY.DOC - Complete HOW TO of polygons
  3.  
  4.         released 12-01-94
  5.         by Voltaire/OTM (Zach Mortensen)
  6.  
  7.         email -
  8.         mortens1@nersc.gov
  9.  
  10.  
  11. INTRODUCTION
  12.  
  13.                 After receiving numerous requests to do so, I have
  14.         compiled a HOW TO doc on polygon filling.  It seems that a lot
  15.         of people out there are a lot like myself, they really dislike
  16.         using other people's code because it is extremely difficult to
  17.         figure out, especially if it is highly optimized.  Sometimes
  18.         text files are the answer, often times however they do more
  19.         harm than good.  When I was writing my 3d engine a few
  20.         erroneous text files caused me to waste several days debugging,
  21.         and in the end I wound up deriving everything on my own.
  22.         Hopefully I have learned from my painful experiences and will
  23.         make my explanations clear and concise, yet still offer ideas
  24.         for optimization.  My polygon routines are among the fastest I
  25.         have seen on the PC, but that is only because I am a
  26.         perfectionist obsessed with being the best ;)
  27.  
  28.                 I have attempted to arrange the sections of this
  29.         document in a somewhat logical fashion; that is, you should 
  30.         feel confident in one area before attempting to move on to the 
  31.         next.  This is the order in which I did things, so I figured 
  32.         what worked for me will most likely work for everyone else as 
  33.         well.  For my code examples I will use C++, and I will give 
  34.         ideas for assembler optimizations of these routines.
  35.  
  36.                 Everything here applies to triangles only, although it
  37.         is possible to adapt the techniques used here to four sided
  38.         polygons (quadrangles?) as well.  Why use triangles?  The
  39.         simple and straightforward answer: triangles are the most
  40.         mathematically correct.  Take any three points in space, ANY
  41.         three, and you can make a two dimensional plane out of them.
  42.         If you were to take four points on the other hand, it is very
  43.         likely that you will end up with a 3 dimensional shape instead.
  44.         Triangles also make vector operations (dot and cross product)
  45.         much simpler.
  46.  
  47.  
  48. SCAN CONVERSION
  49.  
  50.                 The process of determining the coordinates along the
  51.         edges of a polygon is known as SCAN CONVERSION.  The name comes
  52.         from the fact that most people use the horizontal scanlines on
  53.         the screen as the basis for doing this.  You will need to scan
  54.         convert each edge of every polygon you draw, and to do this you
  55.         need two points (the endpoints of the edge) P1 (X1, Y1) and P2
  56.         (X2, Y2)
  57.  
  58.  
  59.               P1 .  (X1, Y1)
  60.                   \
  61.                     \
  62.                       \
  63.                         \
  64.                           \
  65.                             \
  66.                               \
  67.                             P2  \.  (X2, Y2)
  68.  
  69.  
  70.         Now it's time to take a little trip back in time to the Algebra
  71.         you suffered through in Junior High (if you ever thought you
  72.         could be a coder without using any math you may as well turn
  73.         off your computer right now and throw it out the window).  The
  74.         equation
  75.  
  76.  
  77.                 y = mx + b
  78.  
  79.  
  80.         should be indelibly etched in your mind.  To refresh the memory 
  81.         those who were asleep in class, m is the slope (change in 
  82.         Y/change in X or dy/dx if you are a calculus type), and b is 
  83.         the y intercept (value of y when x = 0).  A variation of this 
  84.         formula you learned in your Algebra II class is
  85.  
  86.  
  87.                 y - Y1 = m(x - X1)
  88.  
  89.  
  90.         which is much more useful in our case, because we really don't
  91.         care about the value of y at (x = 0).
  92.  
  93.  
  94.                 What I am about to say will confuse a lot of you, but
  95.         I'm going to say it anyway.  When filling polygons, we need to
  96.         switch all the Xs and Ys in the above equation.  WHY???  The
  97.         reason is simple: the above equation is solved for y in terms
  98.         of x, which means "you feed me an x value, and I'll tell you
  99.         the y value at that point."  When filling polygons, we would
  100.         much rather draw horizontal lines than vertical lines, because
  101.         horizontal line drawing is MUCH easier and much faster.
  102.         Therefore, we want an equation that will give us the x value at
  103.         a given y:
  104.  
  105.  
  106.                 x - X1 = m(y - Y1)
  107.  
  108.  
  109.         That's simple enough, but there is a catch.  The m in the above
  110.         equation is no longer dy/dx, instead it is dx/dy.  This is
  111.         purely logical, dy/dx can be read as "change in y with respect
  112.         to change in x."  Since we are changing y instead of x in the
  113.         above equation, it is logical to have m = "change in x with
  114.         respect to change in y."
  115.  
  116.                 When you begin scan converting an edge, you will only
  117.         know X1, Y1, X2, and Y2.  Therefore, you need to calculate m.
  118.         The formula for this is
  119.  
  120.  
  121.                     (X2 - X1)
  122.                 m = ---------
  123.                     (Y2 - Y1)
  124.  
  125.  
  126.                 Once we have a value for m, we can determine the x
  127.         value of any given y.  We will find x values for all integer y 
  128.         values between Y1 and Y2 in order to determine the edges of our 
  129.         polygon, so we know where to start and stop drawing horizontal
  130.         lines when we fill it.  One equation which will give us these
  131.         values is
  132.  
  133.  
  134.                 x = m(y - Y1) + X1
  135.  
  136.  
  137.                 Now, you could solve this equation for every integer y 
  138.         value between Y1 and Y2, but this is extrememly slow.  If you 
  139.         remember that we are starting at y = Y1 and we only interested 
  140.         in the value of x at each scanline (we are using integers for 
  141.         y), you can do a little trick to simplify this equation:
  142.  
  143.  
  144.                 point 0 -> x = m((Y1 + 0) - Y1) + X1
  145.                 point 1 -> x = m((Y1 + 1) - Y1) + X1
  146.                 point 2 -> x = m((Y1 + 2) - Y1) + X1
  147.  
  148.                                 ...
  149.  
  150.                 point n -> x = m((Y1 + n) - Y1) + X1
  151.  
  152.  
  153.         It is obvious that ((Y1 + n) - Y1) is simply n, so we arrive at
  154.         the general equation
  155.  
  156.  
  157.                 x = m(n) + X1
  158.  
  159.  
  160.         Now, if we take the difference between two consecutive x
  161.         values (x values at scanlines n + 1 and n), we find that
  162.  
  163.  
  164.                   (m(n + 1) + X1) - (m(n) + X1)
  165.                 = mn + m + X1 - mn - X1
  166.                 = m
  167.  
  168.  
  169.         WOW, that makes life easy.  The difference between the x values
  170.         of consecutive y values is simply m.  That means that we now
  171.         have a recursive definition for x (recursive means that the
  172.         value of each term is based on the value of the previous one)
  173.  
  174.  
  175.                 Xn = X(n-1) + m
  176.  
  177.  
  178.         So by adding m to the x value of the previous scanline, we can 
  179.         determine the x value at the current scanline.  Of course we 
  180.         also know the starting point of this sequence, X1.  Some C++ 
  181.         code that implements this might look like
  182.  
  183.  
  184.         float x, m, edge[200];          // use edge to store the x
  185.         int count;                      // values at each scanline,
  186.                                         // there are a maximum of 200
  187.                                         // lines on the screen, so we
  188.                                         // need room for 200 x values
  189.  
  190.  
  191.         m = (X2 - X1)/(Y2 - Y1);
  192.         x = X1;
  193.  
  194.         for (count = Y1; count < Y2; count++)
  195.         {
  196.             edge[count] = x;
  197.             x += m;
  198.         }
  199.  
  200.  
  201.         There is another problem here, we are dealing with floating
  202.         point numbers, which are incredibly slow in calculations if 
  203.         you don't have a coprocessor.  The solution to this problem is 
  204.         to use fixed point integer math.  In fixed point, you multiply 
  205.         each number by a scaling factor.  When you perform any
  206.         calculations, divides in particular, the precision of your
  207.         answer is accurate to a fixed number of decimal places, hence
  208.         the name fixed point.
  209.  
  210.                 The most common implementations of fixed point scale by
  211.         factors of either 256 or 65536 because multiplying and dividing
  212.         by these numbers can be accomplished by shifting bits, and each
  213.         of these numbers is equivalent to half a register in assembler.
  214.         In order to use 16 bit or 8.8 fixed point, scale by 256 (you
  215.         now have 8 bits for the integer part and 8 bits for the decimal
  216.         part), and scale by 65536 in order to use 32 bit or 16.16 fixed
  217.         point (16 integer bits, 16 decimal bits).  When you are done
  218.         with all your calculations and need an integer answer, you
  219.         simply use the top 8 or 16 bits of your fixed point integer,
  220.         depending on what your scale factor was.
  221.  
  222.         The above example converted to fixed point would look something
  223.         like this
  224.  
  225.  
  226.         int x, m, count, edge[200];     // if you are using 16 bit
  227.                                         // code, these should be of
  228.                                         // type long rather than int
  229.  
  230.         m = (X2 - X1) << 16;            // dx scaled to 16.16 fixed
  231.                                         // point, I'm assuming you are
  232.                                         // using 32 bit code
  233.  
  234.  
  235.         m /= (Y2 - Y1)                  // notice that you do NOT scale
  236.                                         // the denominator, if you did
  237.                                         // you would lose all precision
  238.                                         // in your answer
  239.  
  240.         x = X1 << 16;                   // scale the starting x value
  241.  
  242.         for (count = Y1; count < Y2; count++)
  243.         {
  244.             edge[count] = x >> 16;      // store only the integer part
  245.             x += m;
  246.         }
  247.  
  248.  
  249.                 Now that you have an incredibly fast way to scan
  250.         convert a single edge, you must do this for all the edges in
  251.         your polygon.  Here's another place where triangles are better
  252.         than quadrangles: on any triangle, there is a top, middle, and
  253.         bottom point.  The side connecting the top and bottom points is
  254.         ALWAYS the longest.  Therefore, the best strategy for scan
  255.         converting an entire triangle is as follows:
  256.  
  257.         1.  Set up two edge lists, one for the right and one for the
  258.             left side of each scanline.
  259.  
  260.         2.  Scan convert the longest edge and store it in the left edge
  261.             list.
  262.  
  263.         3.  Scan convert the remaining two edges and store them in the
  264.             right edge list.
  265.  
  266.         4.  Well...FILL IT OF COURSE!
  267.  
  268.  
  269. FLAT FILLING
  270.  
  271.                 Flat filling is the simplest and least impressive form
  272.         of polygon filling.  The entire polygon is filled with one
  273.         color.  In order to flat fill a polygon, you need to write two
  274.         routines
  275.  
  276.         1.  A routine to scan convert an entire polygon
  277.  
  278.         2.  A routine to draw a horizontal line
  279.  
  280.         THAT'S IT!
  281.  
  282.  
  283.                 Horizontal line drawing is very simple in chained
  284.         (packed pixel) video modes such as VGA mode 13h.  For the sake
  285.         of simplicity, I will use mode 13h as the example here.
  286.  
  287.                 Your horizontal line routine should definitely be in
  288.         assembler, because it is going to get called a LOT.
  289.         Preferably, it will be integrated into your poly filler so you
  290.         don't have to waste time pushing arguments and calling another
  291.         procedure.
  292.  
  293.                 To draw a horizontal line you need to know 4 things:
  294.         the starting and ending x values (X1, X2), the y value (Y), and
  295.         the color (C).
  296.  
  297.                 The first thing to do is make sure that X1 < X2.  If
  298.         not, switch them before you continue.
  299.  
  300.                 I'm not going to cover mode 13h graphics basics here
  301.         because they are too basic.  If you don't know mode 13h yet,
  302.         you have no business writing anything until you learn it!  The
  303.         easiest way to draw a horizontal line in mode 13h is to
  304.  
  305.         1.  Determine the starting memory address of the line (A000h +
  306.             (320 * Y) + X1)
  307.  
  308.         2.  Determine the length of the line in pixels
  309.  
  310.         3.  Store (length) bytes of value (color) starting at the
  311.             starting memory address.
  312.  
  313.                 In order to make use of the 32 bit processor in your
  314.         machine, you will want to store doublewords instead of bytes.
  315.         This makes flat filling in mode 13h almost as fast as flat
  316.         filling in mode x.  Too store doublewords, you need to
  317.  
  318.         1.  Perform steps 1 and 2 listed above
  319.  
  320.         2.  Convert your byte color value into a dword (just make it 4
  321.             bytes in a row of your original color)
  322.  
  323.         3.  Store (length / 4) doublewords
  324.  
  325.         4.  Store (length % 4) bytes  (a quick way to do (length % 4)
  326.             is (length & 3))
  327.  
  328.  
  329.                 Once you have your horizontal line routine up and
  330.         running, you need to integrate it into your scan converter.
  331.         The easiest way to do this is to make a loop from Y1 to Y2
  332.         where you draw horizontal lines between the edges of the
  333.         polygon.  Here's some pseudo code
  334.  
  335.         scanLongSide();
  336.         scanMidSide();
  337.         scanShortSide();
  338.  
  339.         for (count = Y1; count < Y2; count++)
  340.             hline(lEdge[count], rEdge[count], count, color);
  341.  
  342.  
  343.         Easy as pi...
  344.  
  345.  
  346. CLIPPING
  347.  
  348.                 We quicly run into a problem in mode 13h, images drawn
  349.         off the screen wrap around to the next scanline.  This is not
  350.         very aesthetically pleasing to say the least.  In protected
  351.         mode, this poses an even nastier problem.  Any memory writes
  352.         outside the 64k window reserved for the VGA produce a general
  353.         protection fault, very nasty.  The answer to these problems is 
  354.         to "stay inside the lines" when we are drawing, to "clip" our 
  355.         drawings so we only draw what is physically on screen.
  356.  
  357.                 When incorporated in a poly filler, the most
  358.         rudimentary form of clipping checks y values at scan conversion
  359.         time and x values when drawing horizontal lines.
  360.  
  361.         Scan conversion C++ code that implements y clipping would look
  362.         somewhat like this
  363.  
  364.  
  365.         for (count = Y1; count < Y2; count++)
  366.         {
  367.             if ((count >= 0) && (count < 200))
  368.                 edge[count] = x;
  369.             x += m;
  370.         }
  371.  
  372.  
  373.         Notice that you change the x value every time through the loop,
  374.         but you only store the edges that are on the screen.  This
  375.         assures that the x values you get later in the loop are
  376.         accurate.
  377.  
  378.                 Clipping in the x direction is best if done in the
  379.         horizontal line routine, and is even easier to implement than
  380.         y clipping in the scan conversion.  Once you make sure
  381.         that X1 < X2, all you need to do is substitute 0 for X1
  382.         if X1 is smaller than 0, and 319 for X2 if X2 is greater
  383.         than 0.  Also, if X1 > 319 or X2 < 0 you don't need to
  384.         draw anything, the line is entirely off screen.
  385.  
  386.                 Some more complicated clipping that will improve your
  387.         performance significantly involves checking to see if the
  388.         polygon is on screen before you draw.  Check your vertices to
  389.         see if the maximum x value is less than 0, the minimum x value
  390.         is greater than 319, the maximum y value is less than 0, or the
  391.         minimum y value is greater than 199.  If any of these are true,
  392.         your polygon is completely off screen and you have no need to
  393.         scan convert or fill.
  394.  
  395.  
  396. GOURAUD SHADING
  397.  
  398.                 If you have never heard of or seen gouraud shading,
  399.         I pity you.  It is the easiest way to make your boring flat 
  400.         shaded polygons come to life.  In order to explain gouraud
  401.         shading, I ask you to bear with my while I digress and explain
  402.         a bit about 3d math.
  403.  
  404.                 According to Lambert's law, the intensity of light
  405.         falling on a plane is directly related to the angle made when
  406.         the light vector intersects the normal vector of a plane.
  407.         Lambert shading a polygon involves taking the dot product of
  408.         the normal vector and the vector of the light intersecting it,
  409.         which gives the cosine of the angle made by the intersection.
  410.         Based on this value, the color of a plane can be calculated.
  411.         I'm not going to give any further explanation than this because
  412.         I don't want to be up all night typing.
  413.  
  414.                 Gouraud shading is a simple extension of Lambert
  415.         shading.  Instead of finding the intensity of light falling on
  416.         a plane, you determine the average intensity of light at a
  417.         vertex based on a normalized average of the normal vectors of
  418.         all the planes that share that point.  Once again, I'm not
  419.         going to give any further explanation than this of the math
  420.         behind Gouraud shading.  Suffice to say that Gouraud shading
  421.         uses a separate color value for each vertex of each polygon
  422.         rather than a single color for the entire plane.
  423.  
  424.                 After mathematically determining the color of each
  425.         vertex, the color of each point in the plane can be 
  426.         approximated using linear interpolation.  First, the color 
  427.         values along the edges are interpolated between the color 
  428.         values at each vertex.  Then color values along each scanline 
  429.         are interpolated between the color values at the edges of the 
  430.         line
  431.  
  432.  
  433.         Start with      /16 interpolate  16/16  interpolate  16/16
  434.         color values  / /   edge        E/ /E   scanline    E/E/E 
  435.         at vertices /  /    colors    C/  /D    colors    C/CD/D 
  436.                   /   /             A/   /B             A/AAB/B 
  437.                 /    /            8/    /A            8/899A/A 
  438.               /     /           6/     /8           6/67778/8 
  439.             /      /          4/      /7          4/455667/7 
  440.           /       /         2/       /5         2/2333445/5 
  441.         /--------/        0/--------/4        0/--------/4
  442.         0       4          001122334           001122334
  443.  
  444.         step 1             step 2              step 3
  445.  
  446.  
  447.                 Scan conversion is a form of linear interpolation in 
  448.         which x values are interpolated between two known points.  
  449.         Scan conversion for Gouraud shading very similar, but instead 
  450.         of interpolating only x values, we interpolate x values and 
  451.         colors.  A Gouraud polygon filler must be given more 
  452.         information than a flat filler.  For each vertex, we need to 
  453.         know (X, Y, C) instead of just (X, Y).  As I said before, we 
  454.         also need to trace color values while scan converting.  This 
  455.         makes the scan conversion more complicated, however, color is 
  456.         traced (interpolated) in exactly the same way as x
  457.  
  458.  
  459.         mx  = (X2 - X1) << 16;
  460.         mx /= (Y2 - Y1);
  461.  
  462.         mc  = (C2 - C1) << 16;
  463.         mc /= (Y2 - Y1);
  464.  
  465.         x = X1 << 16;                   // scale the starting x value
  466.         c = C1 << 16;                   // scale the starting c value
  467.  
  468.         for (count = Y1; count < Y2; count++)
  469.         {
  470.             edge[count] = x >> 16;      // store only the integer part
  471.             color[count] = c >> 16;     // store only the integer part
  472.             x += mx;
  473.             c += mc;
  474.         }
  475.  
  476.  
  477.                 The resulting color values are stored in color lists
  478.         corresponding with the existing edge lists.  Now we have two x
  479.         values and two color values for each scanline.  The color
  480.         values for each scanline are not equal, so we need to
  481.         interpolate colors along each scanline as we draw.  This is
  482.         done almost exactly as scan conversion is done, with the
  483.         sole exception that we use dc/dx instead of dx/dy.
  484.  
  485.                 The Gouraud horizontal line routine needs to be passed
  486.         x values for the start and end of each scanline (X1, X2) as
  487.         well as color values (C1, C2) and a y value (Y).  Once
  488.         again, you need to make sure that X1 < X2.  If you switch
  489.         X1 and X2, be sure to switch C1 and C2 as well!  Here is some 
  490.         C++ code for a gouraud hline routine
  491.  
  492.         mc  = (C2 - C1) << 8;
  493.         mc /= (X2 - X1);
  494.  
  495.         c = C1 << 8;
  496.  
  497.         for (count = X1; count < X2; count++)
  498.         {
  499.             setPixel(count, y, c >> 8);  // set pixel at (count, y) to
  500.                                          // color c
  501.             c += mc;
  502.         }
  503.  
  504.  
  505.         Notice that I used 8.8 fixed point here.  This is because you
  506.         are GUARANTEED that you will not have any color greater than
  507.         255 when you are using mode 13h, so you can shift the value
  508.         left 8 bits without any word overflow.  The reason you want to
  509.         use 8.8 fixed point is so you can do a little trick when you
  510.         convert to assembler that will allow you to eliminate the shift
  511.         right
  512.  
  513.         ; first, get the starting memory address of the line in edi
  514.         ; and the number of pixels to draw in ecx
  515.  
  516.         mov edx, mc     ; (dc/dx * 256)
  517.         mov ebx, C1     ; starting color
  518.         shl ebx, 8      ; C1 * 256
  519.  
  520.         ghlLoop:
  521.  
  522.         mov [edi], bh   ; draw the upper 8 bits (integer part)
  523.         add ebx, edx    ; c += mc
  524.         inc edi         ; go to next screen location
  525.  
  526.         dec ecx         ; pixels to draw --
  527.         jnz ghlLoop
  528.  
  529.  
  530.         That inner loop is VERY FAST, it should almost run in one
  531.         memory wait state, which means that you get all the cpu cycles
  532.         for free while you are waiting to be able to write to memory
  533.         again.
  534.  
  535.         Clipping here is implemented exactly the same as for flat
  536.         polygons.
  537.  
  538.  
  539.  
  540. Z-BUFFERING
  541.  
  542.                 Ah yes, the crux of the biscuit indeed.  Z-buffering is
  543.         a technique used by more advanced 3d systems, it allows you to
  544.         do several things.  First, z-buffering speeds up 3d code by
  545.         eliminating plane sorting.  You can draw planes in any order,
  546.         and they will come out looking right.  This is a big
  547.         advantage when drawing objects with many faces, because sorting
  548.         routines take exponentially longer to sort larger data sets.
  549.         Second, z-buffering correctly draws intersecting polygons
  550.         WITHOUT having to calculate where they intersect, which
  551.         produces some impressive effects and doesn't require any extra
  552.         calculation.
  553.  
  554.                 Z-buffering accomplishes these feats by interpolating z
  555.         values between vertices and scanline edges much in the same way
  556.         Gouraud shading interpolates color.  The resulting z values for
  557.         each pixel on the screen are stored in a 'z-buffer' containing
  558.         (screenWidth * screenHeight) elements (64000 in mode 13h).
  559.         Before a new pixel is drawn, the z-buffer value for that
  560.         pixel is examined.  If the new pixel's z-value is less than
  561.         (closer to the viewer than) the z-buffer value, the new z value
  562.         is stored in the z-buffer and the pixel is drawn to the screen.
  563.         Otherwise, the z-buffer and the screen remain unchanged.
  564.         Through this process, the pixel at each screen location which
  565.         is closest to the viewer (and therefore not obscured by
  566.         anything else) is always displayed.
  567.  
  568.                 Because we don't want to have a 3d world which is only
  569.         128 pixels deep, the z-buffer elements cannot be bytes; we need
  570.         to store at least 16 bits of z data in each array element.
  571.         This allows us to have a world which is 32768 pixels deep,
  572.         adequate for most applications.  Right away, we see a big
  573.         problem with z-buffering, MEMORY!  It takes a LOT of memory to
  574.         store all those z values, 128k for a 16 bit z-buffer in mode
  575.         13h.  In my opinion, the advantages of z-buffering disappear
  576.         when you are in real mode due to the incredible amount of
  577.         memory required, so switch to protected mode before attempting
  578.         to implement z-buffering.
  579.  
  580.                 As I said before, z-buffering is almost identical to
  581.         Gouraud shading, with the exception that z values are
  582.         interpolated instead of color values.  Of course, you need to
  583.         pass some more information to your z-buffered poly routine,
  584.         namely the z values of each vertex. When scan converting, store
  585.         z values in z lists the same way you store color values in
  586.         color lists.  Here's some C++ code
  587.  
  588.         mx  = (X2 - X1) << 16;
  589.         mx /= (Y2 - Y1);
  590.  
  591.         mz  = (Z2 - Z1) << 16;
  592.         mc /= (Y2 - Y1);
  593.  
  594.         x = X1 << 16;                   // scale the starting x value
  595.         z = Z1 << 16;                   // scale the starting z value
  596.  
  597.         for (count = Y1; count < Y2; count++)
  598.         {
  599.             edge[count] = x >> 16;      // store only the integer part
  600.             zval[count] = z >> 16;      // store only the integer part
  601.             x += mx;
  602.             z += mz;
  603.         }
  604.  
  605.  
  606.                 When you are tracing horizontal lines, the z values are
  607.         interpolated in exactly the same way as they are with Gouraud
  608.         shading.  After you determine a z value, check it against the z
  609.         value stored in the z-buffer for the current screen location to
  610.         see if the current pixel is visible.  If it is, draw it;
  611.         otherwise skip it and go on.  Here's some C++ again
  612.  
  613.         mz  = (Z2- Z1) << 16;
  614.         mz /= (X2 - X1);
  615.  
  616.         z = Z1 << 16;
  617.  
  618.         for (count = X1; count < X2; count++)
  619.         {
  620.             if ((z >> 16) < zBuffer[y * 320 + x])
  621.             {
  622.                 setPixel(count, y, c);
  623.                 zBuffer[y * 320 + x] = z >> 16;
  624.             }
  625.  
  626.             z += mz;
  627.         }
  628.  
  629.                 Of course, you don't perform the slow z-buffer address
  630.         calculation every time through the loop.  Just find the
  631.         starting offset into the screen, use that to find the starting
  632.         offset into the z-buffer, and then increment the z-buffer
  633.         pointer by 2 (for words) every time through the loop.  Also
  634.         make sure you use 16.16 fixed point here.
  635.  
  636.                 Don't forget to clear the z-buffer every time you clear 
  637.         the screen.  This is accomplished by filling it with 32767 
  638.         (7fffh), or whatever your maximum depth is.
  639.  
  640.                 By now you should know how to clip, it's exactly the
  641.         same for z-buffered polygons as it is for all others.
  642.  
  643.                 Notice that all the above examples deal with flat
  644.         shaded polygons.  This is because once you have written a
  645.         Gouraud poly filler, it will take you about a half hour to
  646.         convert it to a flat z-buffered filler if you were smart about
  647.         how you wrote your code.  If you are feeling REALLY brave, you
  648.         should try writing a Gouraud shaded z-buffered poly routine;
  649.         but let me warn you - YOU _WILL_ RUN OUT OF REGISTERS!  Besides
  650.         that, the code will be huge.  My gouraud shaded z-buffered poly
  651.         routine was well over 1200 lines of assembler, about twice as
  652.         long as this file!  But it's by no means impossible, and it's a
  653.         lot of fun just sitting and watching two gouraud shaded objects
  654.         intersecting each other when you are done.
  655.  
  656.  
  657. CLOSING COMMENTS
  658.  
  659.                 WOW, I didn't intend to write this entire text in one
  660.         night.  Oh well, at least it's done.  Hopefully I have been of
  661.         some assistance to just about everyone who reads this file.  I
  662.         apologize if some of the basics were too simple or if some of
  663.         the more complex points were not covered in enough detail, but
  664.         that is what you risk by catering to such a wide group of
  665.         people.
  666.  
  667.                 If you need any tips on optimization feel free to mail
  668.         me (mortens1@nersc.gov).  The source to my flat and Gouraud
  669.         poly fillers has already been released as part of my 3d engine
  670.         (V3DT090.ZIP availible via ftp at hornet.eng.ufl.edu
  671.         /demos/code/library/graph/), and my z-buffering code will be
  672.         released shortly (as soon as I clean it up and document it so
  673.         that it is READABLE ;))
  674.  
  675.  
  676. GREETS
  677.  
  678.  
  679.         Siri                    - I LOVE YOU I LOVE YOU I LOVE YOU
  680.         All of OTM              - What can I say...we rule ;)
  681.         Hurricane/OTM           - TED LIVES!
  682.         Zilym Limms/OTM         - I can't wait to convert OP to pmode ;)
  683.         Alex Chalfin/OTM        - we need to get you a handle ;)
  684.         Patrick Aalto           - for explaining Gouraud shading to me
  685.         Arnold                  -_
  686.         Hans                    -_- Larry Liverwurst Natural Lavatory
  687.         Lothar                  -
  688.         Tran & DareDevil        - for PMODE/W
  689.         All my #coders buddies...(no particular order)
  690.         Bone_
  691.         Trinel
  692.         ShadowH
  693.         bri_acid
  694.         OC
  695.         Addict
  696.         doom
  697.         StarScrm
  698.         MainFrame
  699.         BEAn_dIP
  700.         GodHead
  701.         Moomin
  702.         Zep
  703.         Druggie
  704.         Stimpee
  705.         pel
  706.         Opium
  707.         PhulAdder
  708.         Shades
  709.         BarryE
  710.         MindRape
  711.